﻿
#include <stdio.h>
#include <direct.h>
#include <io.h>
#include <atlconv.h>
#include "d2super_loader_common.h"

//如果需要隐藏DOS窗口的话，就放开下面这句
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")

enum VideoMode
{
    gdi = 1,
    software = 2,
    ddraw = 3,
    glide = 4,
    opengl = 5, //UNUSED
    d3d = 6,
    rave = 7 //UNUSED
};

enum GameRes
{
    res_640x480 = 0,
    res_800x600 = 2
};

enum GameMode
{
    none = 0x0,
    client = 0x1,
    server = 0x2,
    multiplayer = 0x3,
    launcher = 0x4,
    expand = 0x5
};

typedef DWORD(__stdcall *type_pfnStormRegLoadString)(const char *keyname, const char *valuename, int a3, char *buffer, size_t buffersize);
typedef DWORD(__stdcall *type_pfnStormRegLoadValue)(const char *keyname, const char *valuename, int a3, int *value);
typedef DWORD(__stdcall *type_pfnStormOpenArchive)(LPCSTR lpFileName, DWORD dwPriority, DWORD dwFlags, HANDLE *hMPQ);
typedef DWORD(__stdcall *type_pfnStormCloseArchive)(HANDLE hMPQ);
typedef int(__fastcall *type_pfnStormSprintf)(void *, DWORD, const char *, ...);
typedef void(__fastcall *type_pfnFogSetLogPrefix)(const char *szPrefix);
typedef void(__fastcall *type_pfnFogSetErrorHandler)(const char *szApp, void *fnCallback, const char *szVersion, BOOL bAddInfo);
typedef void(__fastcall *type_pfnFogSetFileOptions)(BOOL bDirect, BOOL bQuickSeek);
typedef void(__fastcall *type_pfnFogSetAsyncData)(BOOL bState, DWORD dwUnknown);
typedef void(__fastcall *type_pfnFogFreeAsyncData)();
typedef BOOL(__fastcall *type_pfnIsErrorState)();
typedef void(__fastcall *type_pfnFogInit)();
typedef BOOL(__fastcall *type_pfnIsErrorState)();
typedef void(__fastcall *type_pfnErrorRescue)();
typedef void(__stdcall *type_pfnFreePools)(void *pParams);
typedef void(__stdcall *type_pfnSetServerParams)(void *pParams);
typedef BOOL(__fastcall *type_pfnFogIsExpansion)();
typedef BOOL(__stdcall *type_pfnD2WinLoadMPQs)();
typedef BOOL(__fastcall *type_pfnD2WinLoadExpansionMPQs)(int *, int *, BOOL, void *);
typedef void(__stdcall *type_pfnD2WinUnloadMPQs)();
typedef BOOL(__stdcall *type_pfnD2WinInitGfx)(HINSTANCE hInstance, int nDriver, BOOL bWindowed, BOOL bGFXCompress);
typedef BOOL(__stdcall *type_pfnD2WinDeinitGFX)();
typedef BOOL(__stdcall *type_pfnD2WinCreateWindow)(BOOL bWindowed, int ResoulutionMode);
typedef DWORD(__fastcall *type_pfnD2LaunchCall)(void *pvParam);
typedef void *(__stdcall *type_pfnD2GetHwnd)();
typedef void(__stdcall *type_pfnD2GfxRelease)();
typedef DWORD(__stdcall *type_pfnD2LangGetUsedLanguage)();
typedef void(__fastcall *type_pfnD2SoundInit)(BOOL bExpansion, BOOL bSoundBackground);
typedef DWORD(__fastcall *type_pfnD2Common10097)();
typedef void(__fastcall *type_pfnD2Fog10082)();
typedef void(__stdcall *type_pfnD2gfx10068)(DWORD);
typedef void(__stdcall *type_pfnD2gfx10071)(DWORD);
typedef int(__stdcall *type_pfnStorm426)(const char *, const char *, DWORD, DWORD);

static type_pfnStormRegLoadString m_pfnStormRegLoadString;
static type_pfnStormRegLoadValue m_pfnStormRegLoadValue;
static type_pfnStormOpenArchive m_pfnStormOpenArchive;
static type_pfnStormCloseArchive m_pfnStormCloseArchive;
static type_pfnStormSprintf m_pfnStormSprintf;
static type_pfnFogSetLogPrefix m_pfnFogSetLogPrefix;
static type_pfnFogSetErrorHandler m_pfnFogSetErrorHandler;
static type_pfnFogSetFileOptions m_pfnFogSetFileOptions;
static type_pfnFogSetAsyncData m_pfnFogSetAsyncData;
static type_pfnFogInit m_pfnFogInit;
static type_pfnFogIsExpansion m_pfnFogIsExpansion;
static type_pfnIsErrorState m_pfnIsErrorState;
static type_pfnErrorRescue m_pfnErrorRescue;
static type_pfnFogFreeAsyncData m_pfnFogFreeAsyncData;
static type_pfnFreePools m_pfnFreePools;
static type_pfnSetServerParams m_pfnSetServerParams;
static type_pfnD2WinLoadMPQs m_pfnD2WinLoadMPQs;
static type_pfnD2WinLoadExpansionMPQs m_pfnD2WinLoadExpansionMPQs;
static type_pfnD2WinUnloadMPQs m_pfnD2WinUnloadMPQs;
static type_pfnD2WinInitGfx m_pfnD2WinInitGfx;
static type_pfnD2WinDeinitGFX m_pfnD2WinDeinitGFX;
static type_pfnD2WinCreateWindow m_pfnD2WinCreateWindow;
static type_pfnD2GetHwnd m_pfnD2GetHwnd;
static type_pfnD2GfxRelease m_pfnD2GfxRelease;
static type_pfnD2GfxRelease m_pfnD2LangFree;
static type_pfnD2LangGetUsedLanguage m_pfnD2LangGetUsedLanguage;
static type_pfnD2SoundInit m_pfnD2SoundInit;
static type_pfnD2GfxRelease m_pfnD2SoundShutdown;
static type_pfnD2Common10097 m_pfnD2Common10097;
static type_pfnD2Fog10082 m_pfnD2Fog10082;
static type_pfnD2gfx10068 m_pfnD2GfxSetPerspective;
static type_pfnD2Fog10082 m_pfnD2GfxSetLowQuality;
static type_pfnD2Fog10082 m_pfnD2GfxSetGameName;
static type_pfnD2gfx10071 m_pfnD2GfxSetGamma;
static type_pfnD2Fog10082 m_pfnD2GfxFixAspectRatio;
static type_pfnStorm426 m_pfnStormSetResolution;
static type_pfnD2Fog10082 m_pfnD2WinUninit;
static type_pfnD2Fog10082 m_pfnD2MCPClientUninit;
static type_pfnD2Fog10082 m_pfnD2Common10925;
;

typedef struct
{
    type_pfnD2LaunchCall pfnLaunch;
    DWORD dwUnknown;
} ST_LAUNCH_CALLBACK;

typedef ST_LAUNCH_CALLBACK *(__fastcall *type_pfnD2LaunchGetCb)();
static type_pfnD2LaunchGetCb m_pfnD2LaunchGetCb;
static type_pfnD2LaunchGetCb m_pfnD2ClientGetCb;
static type_pfnD2LaunchGetCb m_pfnD2MultiGetCb;

static const char *m_pcBoxName = "D2Loader";

static int msgBox(LPCSTR boxName, UINT uType, LPCSTR pFormat, ...)
{
    char buffer[300];
    va_list lArgs;
    va_start( lArgs, pFormat );
    vsprintf_s( buffer, sizeof(buffer), pFormat, lArgs );
    va_end(lArgs);

    return MessageBox(NULL, buffer, boxName, uType);
}

static void D2Critical_Callback()
{
    MessageBoxA(NULL, "An critical error occured!\nexit!", m_pcBoxName, MB_OK | MB_ICONASTERISK);
    //exit(1);
}

static BOOL Proc_new()
{
    return D2TRUE;
}

typedef void(__stdcall *type_pfnDllInit)();
static DWORD D2Loader_LoadLibrary(const char *dll)
{
    type_pfnDllInit pfnDllInit = NULL;
    char acDllName[MAX_PATH] = {0};
    const char *pcTemp = strchr(dll, ':');

    if (NULL != pcTemp)
    {
        memcpy(acDllName, dll, (DWORD)(pcTemp - dll));
        pcTemp++;
    }
    else
    {
        strcpy_s(acDllName, sizeof(acDllName), dll);
    }

    DWORD module = (DWORD)GetModuleHandle(acDllName);
    if (NULL != module)
    {
        return module;
    }

    module = (DWORD)LoadLibraryEx(acDllName, NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
    if (NULL == module)
    {
        msgBox(m_pcBoxName, MB_OK | MB_ICONASTERISK, "Load %s failed!", acDllName);
        exit(1);
    }

    if (NULL != pcTemp && NULL != (pfnDllInit = (type_pfnDllInit)GetProcAddress((HMODULE)module, pcTemp)))
    {
        pfnDllInit();
    }

    return module;
}

static DWORD GetDllOffset2(const char *dll, const char *pcName)
{
    HMODULE module = (HMODULE)D2Loader_LoadLibrary(dll);
    DWORD locatedAddress = (DWORD)GetProcAddress(module, (LPCSTR)pcName);
    return locatedAddress;
}

static DWORD GetDllOffset(const char *dll, int offset)
{
    return GetDllOffset2(dll, (LPCSTR)offset);
}

static void LoadAllLibraries()
{
    const char *apcLibrary[] = {"Storm.dll","Fog.dll", "D2Sound.dll", "D2Lang.dll", "D2Gfx.dll", "D2Win.dll", "D2Launch.dll"};

    for (int i = 0; i < sizeof(apcLibrary) / sizeof(apcLibrary[0]); i++)
    {
        D2Loader_LoadLibrary(apcLibrary[i]);
    }
}

static BOOL D2Loader_InitFuncPtr()
{
    m_pfnStormRegLoadString = (type_pfnStormRegLoadString)GetDllOffset("Storm.dll", 422);
    m_pfnStormSprintf = (type_pfnStormSprintf)GetDllOffset("Storm.dll", 578);
    m_pfnFogSetLogPrefix = (type_pfnFogSetLogPrefix)GetDllOffset("Fog.dll", 10021);
    m_pfnFogSetErrorHandler = (type_pfnFogSetErrorHandler)GetDllOffset("Fog.dll", 10019);
    m_pfnFogSetFileOptions = (type_pfnFogSetFileOptions)GetDllOffset("Fog.dll", 10101);
    m_pfnFogSetAsyncData = (type_pfnFogSetAsyncData)GetDllOffset("Fog.dll", 10089);
    m_pfnFogInit = (type_pfnFogInit)GetDllOffset("Fog.dll", 10218);
    m_pfnD2WinLoadMPQs = (type_pfnD2WinLoadMPQs)GetDllOffset("D2Win.dll", 10174);
    m_pfnD2WinLoadExpansionMPQs = (type_pfnD2WinLoadExpansionMPQs)GetDllOffset("D2Win.dll", 10072);
    m_pfnFogIsExpansion = (type_pfnFogIsExpansion)GetDllOffset("Fog.dll", 10227);
    m_pfnD2WinInitGfx = (type_pfnD2WinInitGfx)GetDllOffset("D2Win.dll", 10071);
    m_pfnD2WinCreateWindow = (type_pfnD2WinCreateWindow)GetDllOffset("D2Win.dll", 10129);
    m_pfnD2SoundInit = (type_pfnD2SoundInit)GetDllOffset("D2Sound.dll", 10023);
    m_pfnD2SoundShutdown = (type_pfnD2GfxRelease)GetDllOffset("D2Sound.dll", 10024);
    m_pfnD2WinDeinitGFX = (type_pfnD2WinDeinitGFX)GetDllOffset("D2Win.dll", 10132);
    m_pfnD2GfxRelease = (type_pfnD2GfxRelease)GetDllOffset("D2Gfx.dll", 10050);
    m_pfnD2WinUnloadMPQs = (type_pfnD2WinUnloadMPQs)GetDllOffset("D2Win.dll", 10079);
    m_pfnD2GetHwnd = (type_pfnD2GetHwnd)GetDllOffset("D2Gfx.dll", 10007);
    m_pfnD2LangFree = (type_pfnD2GfxRelease)GetDllOffset("D2Lang.dll", 10000);
    m_pfnStormRegLoadValue = (type_pfnStormRegLoadValue)GetDllOffset("Storm.dll", 423);
    m_pfnStormOpenArchive = (type_pfnStormOpenArchive)GetDllOffset("Storm.dll", 266);
    m_pfnStormCloseArchive = (type_pfnStormCloseArchive)GetDllOffset("Storm.dll", 252);
    m_pfnIsErrorState = (type_pfnIsErrorState)GetDllOffset("Fog.dll", 10039);
    m_pfnErrorRescue = (type_pfnErrorRescue)GetDllOffset("Fog.dll", 10040);
    m_pfnFogFreeAsyncData = (type_pfnFogFreeAsyncData)GetDllOffset("Fog.dll", 10090);
    m_pfnFreePools = (type_pfnFreePools)GetDllOffset("Fog.dll", 10143);
    m_pfnSetServerParams = (type_pfnSetServerParams)GetDllOffset("Fog.dll", 10185);
    m_pfnD2Fog10082 = (type_pfnD2Fog10082)GetDllOffset("Fog.dll", 10082);
    m_pfnD2Common10097 = (type_pfnD2Common10097)GetDllOffset("D2Common.dll", 10097);
    m_pfnD2GfxSetPerspective = (type_pfnD2gfx10068)GetDllOffset("D2Gfx.dll", 10068);
    m_pfnD2GfxSetLowQuality = (type_pfnD2Fog10082)GetDllOffset("D2Gfx.dll", 10008);
    m_pfnD2GfxSetGameName = (type_pfnD2Fog10082)GetDllOffset("D2Gfx.dll", 10065);
    m_pfnD2GfxSetGamma = (type_pfnD2gfx10071)GetDllOffset("D2Gfx.dll", 10071);
    m_pfnD2GfxFixAspectRatio = (type_pfnD2Fog10082)GetDllOffset("D2Gfx.dll", 10049);
    m_pfnStormSetResolution = (type_pfnStorm426)GetDllOffset("Storm.dll", 426);
    m_pfnD2WinUninit = (type_pfnD2Fog10082)GetDllOffset("D2Win.dll", 10058);
    m_pfnD2MCPClientUninit = (type_pfnD2Fog10082)GetDllOffset("D2MCPClient.dll", 10006);
    m_pfnD2Common10925 = (type_pfnD2Fog10082)GetDllOffset("D2Common.dll", 10925);

    return TRUE;
}

#define PATCH_FINISH 0xFFFFFFFF
#define PATCH_JMP 0x000000E9
#define PATCH_CALL 0x000000E8
#define PATCH_RETN 0x000000C3
#define PATCH_RETN4 0x000004C2
#define PATCH_RETN8 0x000008C2
#define PATCH_RETN0C 0x00000CC2
#define PATCH_RETN10 0x000010C2
#define PATCH_RETN14 0x000014C2
#define PATCH_RETN18 0x000018C2
#define PATCH_RETN1C 0x00001CC2
#define PATCH_NOPBLOCK 0x90909090

typedef struct
{
    DWORD dwAddress;
    DWORD dwData;
    BOOL boolRelative;
    size_t iPatchSize;
} DLLPatchStrc;

static BOOL D2SE_ApplyPatch(void *hGame, const DWORD dwBaseAddress, const DLLPatchStrc *pstPatch)
{
    while (PATCH_FINISH != pstPatch->dwAddress)
    {
        int iReturn = 0;
        DWORD dwAddress = pstPatch->dwAddress;
        if (!dwAddress)
        {
            return FALSE;
        }

        dwAddress += dwBaseAddress;

        DWORD dwData = pstPatch->dwData;
        if (pstPatch->boolRelative)
        {
            dwData = dwData - (dwAddress + sizeof(dwData));
        }

        void *hAddress = (void *)dwAddress;
        DWORD dwOldPage;

        if (pstPatch->iPatchSize > 0)
        {
            BYTE abBuffer[1024];

            for (size_t i = 0; i < pstPatch->iPatchSize; i++)
            {
                abBuffer[i] = (BYTE)dwData;
            }

            VirtualProtect(hAddress, pstPatch->iPatchSize, PAGE_EXECUTE_READWRITE, &dwOldPage);
            iReturn = WriteProcessMemory(hGame, hAddress, &abBuffer, pstPatch->iPatchSize, 0);
            VirtualProtect(hAddress, pstPatch->iPatchSize, dwOldPage, 0);
        }
        else
        {
            VirtualProtect(hAddress, sizeof(dwData), PAGE_EXECUTE_READWRITE, &dwOldPage);
            iReturn = WriteProcessMemory(hGame, hAddress, &dwData, sizeof(dwData), 0);
            VirtualProtect(hAddress, sizeof(dwData), dwOldPage, 0);
        }

        if (0 == iReturn)
        {
            return FALSE;
        }

        pstPatch++;
    }

    return TRUE;
}

static char m_acGlobalMpqPath[MAX_PATH]="";

static DWORD WINAPI D2Loader_GetMpqPath(HMODULE hModule, LPTSTR lpFilename, DWORD nSize)
{
    char acTempPath[MAX_PATH] = {0};
    char acTempPath2[MAX_PATH] = {0};
    DWORD dwResult;
    char *pcTemp;

    dwResult = GetModuleFileName(hModule, acTempPath, sizeof(acTempPath));
    if (0 == dwResult || 0 == acTempPath[0])
    {
        return 0;
    }

    pcTemp = strrchr(acTempPath, '\\');
    if (!pcTemp)
    {
        strcpy_s(lpFilename, nSize, acTempPath);
        return dwResult;
    }

    memcpy(acTempPath2, acTempPath, (DWORD)(pcTemp - acTempPath));
    sprintf_s(&acTempPath2[strlen(acTempPath2)], sizeof(acTempPath2) - strlen(acTempPath2), "\\%s", lpFilename);
    if (_access(acTempPath2, 0) == 0)
    {
        strcpy_s(lpFilename, nSize, acTempPath);
        return dwResult;
    }

    memset(acTempPath2, 0, sizeof(acTempPath2));
    strcpy_s(acTempPath2, sizeof(acTempPath2), m_acGlobalMpqPath);
    while ('\\' == acTempPath2[strlen(acTempPath2) - 1])
    {
        acTempPath2[strlen(acTempPath2) - 1] = 0;
    }
    sprintf_s(&acTempPath2[strlen(acTempPath2)], sizeof(acTempPath2) - strlen(acTempPath2), "\\%s", lpFilename);

    strcpy_s(lpFilename, nSize, acTempPath2);

    return strlen(lpFilename);
}

static DWORD WINAPI D2Loader_GetMpqPathExp(LPCTSTR lpFileName)
{
    char acTempPath[MAX_PATH] = {0};

    if (!strstr(lpFileName, "d2exp.mpq"))
    {
        return GetFileAttributes(lpFileName);
    }

    strcpy_s(acTempPath, sizeof(acTempPath), m_acGlobalMpqPath);
    while ('\\' == acTempPath[strlen(acTempPath) - 1])
    {
        acTempPath[strlen(acTempPath) - 1] = 0;
    }
    sprintf_s(&acTempPath[strlen(acTempPath)], sizeof(acTempPath) - strlen(acTempPath), "\\%s", "d2exp.mpq");
    if (_access(acTempPath, 0) == 0)
    {
        return GetFileAttributes(acTempPath);
    }

    return GetFileAttributes(lpFileName);
}

static DWORD WINAPI D2Loader_GetMpqPathChar(LPCTSTR lpFileName)
{
    char acTempPath[MAX_PATH] = {0};

    if (!strstr(lpFileName, "d2char.mpq"))
    {
        return GetFileAttributes(lpFileName);
    }

    strcpy_s(acTempPath, sizeof(acTempPath), m_acGlobalMpqPath);
    while ('\\' == acTempPath[strlen(acTempPath) - 1])
    {
        acTempPath[strlen(acTempPath) - 1] = 0;
    }
    sprintf_s(&acTempPath[strlen(acTempPath)], sizeof(acTempPath) - strlen(acTempPath), "\\%s", "d2exp.mpq");
    if (_access(acTempPath, 0) == 0)
    {
        return GetFileAttributes(acTempPath);
    }

    return GetFileAttributes(lpFileName);
}

static void D2Loader_PatchMpqPath()
{
    if (0 == m_acGlobalMpqPath[0])
    {
        return;
    }

static const DLLPatchStrc astPatches[] =
            {
                //更新获取mpq的文件路径
                {0x7DF6, PATCH_CALL, FALSE, 0x01}, //call
                {0x7DF7, (DWORD)D2Loader_GetMpqPath, TRUE, 0x00},
                {0x7DFB, PATCH_NOPBLOCK, FALSE, 0x01},

                {PATCH_FINISH} // this must be the last entry in the array!
            };
        static const DLLPatchStrc astPatches2[] =
            {
                //判断是否资料片，d2exp.mpq，否则会判断为非资料片版本
                {0x1FB20, (DWORD)0xBE, FALSE, 0x01},
                {0x1FB21, (DWORD)D2Loader_GetMpqPathExp, FALSE, 0x00},
                {0x1FB25, PATCH_NOPBLOCK, FALSE, 0x01},

                {PATCH_FINISH} // this must be the last entry in the array!
            };
        static const DLLPatchStrc astPatches3[] =
            {
                //判断是否资料片，d2char.mpq，否则会无法开启TCP/IP多人游戏，只能单机
                {0xCCB9, (DWORD)0xBE, FALSE, 0x01},
                {0xCCBA, (DWORD)D2Loader_GetMpqPathChar, FALSE, 0x00},
                {0xCCBE, PATCH_NOPBLOCK, FALSE, 0x01},

                {PATCH_FINISH} // this must be the last entry in the array!
            };

        D2SE_ApplyPatch(GetCurrentProcess(), (DWORD)GetModuleHandle("D2Win.dll"), astPatches);
        D2SE_ApplyPatch(GetCurrentProcess(), (DWORD)GetModuleHandle("Fog.dll"), astPatches2);
        D2SE_ApplyPatch(GetCurrentProcess(), (DWORD)GetModuleHandle("D2Win.dll"), astPatches3);
}

int main(int argc, char **argv)
{
    static ST_CLIENT_DATA stClientData;
    static ST_CLIENT_DATA_EX stClientData_ex;

    char acBuffer[MAX_PATH] = {0};
    ST_LAUNCH_CALLBACK *pstCallBack = NULL;
    DWORD dwGameMode = launcher;
    DWORD dwVideoMode;
    int iFixedAspectRatio;
    BOOL boolInitGfx = FALSE, boolInitSound = FALSE, boolLoadMpqs = FALSE;
    HANDLE hLanguageMPQ = NULL;
    DWORD i;
    HANDLE ahExtendMPQ[MAX_EXTEND_MPQ] = {NULL};
    HANDLE event = OpenEventA(2, D2TRUE, "DIABLO_II_OK");

CoInitialize(0);
    if (event)
    {
        SetEvent(event);
        CloseHandle(event);
    }

    set_default_command(&stClientData, &stClientData_ex);
    parse_command(argc, argv, &stClientData, &stClientData_ex,m_acGlobalMpqPath);

    if(stClientData_ex.m_supermod_path[0])
    {
        _chdir(stClientData_ex.m_supermod_path);
    }
    //加载dll
    LoadAllLibraries();

    //relocate ptrs
    if (TRUE != D2Loader_InitFuncPtr())
    {
        return 0;
    }

    D2Loader_PatchMpqPath();

    stClientData.mpq_callback = Proc_new;

    if (stClientData.glide_mode)
    {
        dwVideoMode = glide;
    }
    else if (stClientData.window_mode)
    {
        dwVideoMode = gdi;
    }
    else
    {
        dwVideoMode = (stClientData.d3d_mode ? d3d : ddraw);
    }

    memset(acBuffer, 0, sizeof(acBuffer));
    if (0 >= m_pfnStormSprintf(acBuffer, sizeof(acBuffer) / sizeof(acBuffer[0]), "v%d.%02d", 1, 13))
    {
        msgBox(m_pcBoxName, MB_OK | MB_ICONEXCLAMATION, "Call Storm.Sprintf failed!");
        goto out;
    }

    m_pfnFogSetLogPrefix("D2");
    m_pfnFogSetErrorHandler(stClientData_ex.m_acGameTitle, D2Critical_Callback, acBuffer, D2TRUE);

    if (NULL != m_pfnD2Common10097)
    {
        m_pfnD2Common10097();
    }

    m_pfnFogSetFileOptions(stClientData.direct, D2FALSE);
    m_pfnFogSetAsyncData(D2TRUE, D2FALSE);
    m_pfnD2Fog10082();
    m_pfnFogInit();
    boolInitGfx = TRUE;

    printf("Loading MPQs...\r\n");
    if (!m_pfnD2WinLoadMPQs())
    {
        msgBox(m_pcBoxName, MB_OK | MB_ICONEXCLAMATION, "Load MPQs failed!");
        goto out;
    }
    boolLoadMpqs = TRUE;
    if (!m_pfnD2WinLoadExpansionMPQs(NULL, NULL, 0, &stClientData))
    {
        msgBox(m_pcBoxName, MB_OK | MB_ICONEXCLAMATION, "Load Expansion MPQs failed!");
        goto out;
    }
    stClientData.expansion = m_pfnFogIsExpansion();

    if (0 != stClientData_ex.m_acLanguageMpq[0] && !m_pfnStormOpenArchive(stClientData_ex.m_acLanguageMpq, 0x1B58, 2, &hLanguageMPQ))
    {
        msgBox(m_pcBoxName, MB_OK | MB_ICONEXCLAMATION, "Load Language MPQ failed!");
        goto out;
    }

    for (i = 0; i < stClientData_ex.m_dwExtendMpq; ++i)
    {
        if (!m_pfnStormOpenArchive(stClientData_ex.m_aacExtendMpq[i], 0x1B58, 2, &ahExtendMPQ[i]))
        {
            msgBox(m_pcBoxName, MB_OK | MB_ICONEXCLAMATION, "Load MPQ %s failed!", stClientData_ex.m_aacExtendMpq[i]);
            goto out;
        }
    }

    printf("Initing GFX...\r\n");
    if (!m_pfnD2WinInitGfx(GetModuleHandle(NULL), dwVideoMode, stClientData.window_mode, !stClientData.no_gfx_compress))
    {
        msgBox(m_pcBoxName, MB_OK | MB_ICONEXCLAMATION, "Init GFX failed!");
        goto out;
    }

    if (stClientData.perspective && dwVideoMode >= glide)
    {
        m_pfnD2GfxSetPerspective(D2TRUE);
    }

    printf("Creating a window...\r\n");
    if (!m_pfnD2WinCreateWindow(stClientData.window_mode, res_800x600))
    {
        msgBox(m_pcBoxName, MB_OK | MB_ICONEXCLAMATION, "Create window failed!");
        goto out;
    }

    if (stClientData.low_quality)
    {
        m_pfnD2GfxSetLowQuality();
    }
    if (stClientData.gamma)
    {
        m_pfnD2GfxSetGamma(stClientData.gamma);
    }
    if (0 != stClientData.game_name[0])
    {
        m_pfnD2GfxSetGameName();
    }
    iFixedAspectRatio = 1;
    m_pfnStormRegLoadValue("Diablo II", "Fixed Aspect Ratio", 0, &iFixedAspectRatio);
    if (stClientData.fix_aspect_ratio || iFixedAspectRatio != 1)
    {
        m_pfnD2GfxFixAspectRatio();
    }
    if (!stClientData.expansion)
    {
        m_pfnStormSetResolution("Diablo II", "Resolution", 0, 0);
    }

    if (!stClientData.no_sound)
    {
        printf("Initing sound system...\r\n");
        m_pfnD2SoundInit(stClientData.expansion, D2FALSE);
        boolInitSound = TRUE;
    }

    for (i = 0; i < stClientData_ex.m_dwExtendPlugin; ++i)
    {
        D2Loader_LoadLibrary(stClientData_ex.m_aacExtendPlugin[i]);
    }

    m_pfnD2LaunchGetCb = (type_pfnD2LaunchGetCb)GetDllOffset2("D2Launch.dll", "QueryInterface");
    m_pfnD2ClientGetCb = (type_pfnD2LaunchGetCb)GetDllOffset2("D2Client.dll", "QueryInterface");
    m_pfnD2MultiGetCb = (type_pfnD2LaunchGetCb)GetDllOffset2("D2Multi.dll", "QueryInterface");

    while (TRUE)
    {
        printf("Entering the game mode: %d\r\n", dwGameMode);

        switch (dwGameMode)
        {
        case launcher:
            pstCallBack = m_pfnD2LaunchGetCb();
            break;

        case client:
            pstCallBack = m_pfnD2ClientGetCb();
            break;

        case multiplayer:
            pstCallBack = m_pfnD2MultiGetCb();
            break;

        default:
            pstCallBack = NULL;
            break;
        }

        if (NULL == pstCallBack)
        {
            break;
        }
        dwGameMode = pstCallBack->pfnLaunch(&stClientData);
    }

out:
    if (!stClientData.no_sound && boolInitSound)
    {
        printf("Shutting down the sound system...\r\n");
        m_pfnD2SoundShutdown();
    }
    if (boolInitGfx)
    {
        printf("Shutting down the GFX...\r\n");
        m_pfnD2WinDeinitGFX();
        m_pfnD2GfxRelease();
    }
    m_pfnD2WinUninit();
    m_pfnFogFreeAsyncData();
    m_pfnD2MCPClientUninit();

    if (NULL != m_pfnD2Common10925)
    {
        m_pfnD2Common10925();
    }
    m_pfnFreePools(NULL);
    if (hLanguageMPQ)
    {
        m_pfnStormCloseArchive(hLanguageMPQ);
    }
    for (i = 0; i < stClientData_ex.m_dwExtendMpq; ++i)
    {
        if (ahExtendMPQ[i])
        {
            m_pfnStormCloseArchive(ahExtendMPQ[i]);
        }
    }
    if (boolLoadMpqs)
    {
        printf("Unloading the MPQs...\r\n");
        m_pfnD2WinUnloadMPQs();
    }
    printf("quit game!\r\n");
CoUninitialize();
    return 0;
}
